﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Wubi.Utils;

[RequireComponent(typeof(RawImage))]
public class UI3DRenderer : MonoBehaviour
{
    public Camera renderCam;
    [SerializeField] public Transform targetObject;
    public int objectLayer;
    public CameraSettings camSettings = new CameraSettings();

    bool _isEnabled;
    bool _isGeneratedCam;
    CameraSettings _origCamSettings;
    RenderTexture _origRtt;
    int _origObjLayer;
    [SerializeField] Transform _tobj;
    public Transform runtimeTargetObj
    {
        get
        {
            if (_tobj == null)
            {
                runtimeTargetObj = targetObject;
            }
            return _tobj;
        }
        set
        {
            var old = _tobj;
            _tobj = value;
            _needInstall = old != value;
            if (_needInstall)
            {
                Uninstall(old);
                Install(value);
            }
            enabled = _tobj != null;
        }
    }
    bool _needInstall = true;

    private void OnEnable()
    {
        if (runtimeTargetObj == null)
        {
            enabled = false;
        }
        else
        {
            Install(runtimeTargetObj);
        }
    }

    private void OnDisable()
    {
        Uninstall(runtimeTargetObj);
    }

    private void Update()
    {
        if (camSettings.parent != runtimeTargetObj)
            camSettings.parent = runtimeTargetObj;
    }

    private void LateUpdate()
    {
        camSettings.ApplyTo(renderCam);
    }
    
    #region Cam Controlling
    void Install(Transform target)
    {
        if (!_needInstall) return;
        _needInstall = false;
        if (Application.isPlaying && target != null) Debug.LogFormat("<color=green>installing</color> {0} @ {1}", target.name, name);
        if (target == null) return;
        InitRenderCam(target);
        CacheCamera();
        camSettings.enabled = true;
        RefreshRenderTexture();
        ToggleImage(true);
        _origObjLayer = target.gameObject.layer;
        target.SetLayer(objectLayer);
    }
    void Uninstall(Transform target)
    {
        if(Application.isPlaying && target != null) Debug.LogFormat("<color=red>uninstalling</color> {0} @ {1}", target.name, name);
        ToggleImage(false);
        ReleaseRenderTexture();
        RestoreCamera();
        if (target != null)
        {
            target.SetLayer(_origObjLayer);
        }
        _needInstall = true;
    }

    void InitRenderCam(Transform target)
    {
        if (renderCam == null)
        {
            var camGo = new GameObject(string.Format("{0}'s UI Cam", target.name), typeof(Camera));
            camGo.transform.SetParent(target, false);
            renderCam = camGo.GetComponent<Camera>();
            renderCam.enabled = false;
            _isGeneratedCam = true;
        }
    }

    void CacheCamera()
    {
        _origCamSettings = CameraSettings.Snapshot(renderCam);
        _origRtt = renderCam.targetTexture;
        if (_isGeneratedCam) // Generated camera should be off when disabled
            _origCamSettings.enabled = false;
    }

    void RestoreCamera()
    {
        if (renderCam == null) return;
        if (_isGeneratedCam) { Destroy(renderCam.gameObject); renderCam = null; }
        else
        {
            renderCam.targetTexture = _origRtt;
            if (_origCamSettings != null && renderCam != null)
            {
                _origCamSettings.ApplyTo(renderCam);
            }
        }
    }
    #endregion

    #region Rendering
    RenderTexture _rtt;
    RawImage _img;
    RawImage Img { get { return _img ?? (_img = GetComponent<RawImage>()); } }
    Vector2 _imgSize;
    private void OnRectTransformDimensionsChange()
    {
        if (!_isEnabled) return;
        RefreshRenderTexture();
    }

    void RefreshRenderTexture()
    {
        if (renderCam == null || Img == null) return;

        var _isDirty = false;
        var curSize = Img.rectTransform.rect.size;
        if (_imgSize == Vector2.zero || curSize != _imgSize || _rtt == null)
        {
            _isDirty = true;
            _imgSize = curSize;
        }
        if (_isDirty)
        {
            _rtt = new RenderTexture((int)_imgSize.x, (int)_imgSize.y, 16, RenderTextureFormat.ARGB32);
        }
        renderCam.targetTexture = _rtt;
    }

    void ReleaseRenderTexture()
    {
        if (_rtt != null) _rtt.Release();
    }

    void ToggleImage(bool isOn)
    {
        if (isOn)
        {
            Img.texture = _rtt;
            Img.enabled = true;
        }
        else
        {
            Img.enabled = false;
            Img.texture = null;
        }
        _isEnabled = isOn;
    }
    #endregion

    #region Nesting
    [System.Serializable]
    public class CameraSettings
    {
        [System.NonSerialized] public Transform parent;
        public Vector3 offset = new Vector3(0, 0, 3);
        public Vector3 eulerAngles = new Vector3(0, 180, 0);
        public float nearClip = 1;
        public float farClip = 10;
        public LayerMask cullingMask = -1;
        public CameraClearFlags clearFlags = CameraClearFlags.SolidColor;
        public float depth;
        public Color backgroundColor;
        public bool orthographic;
        /** Perspective Settings **/
        public float fov = 40;
        /** Orthographic Settings **/
        public float orthoSize = 8;
        [System.NonSerialized] public bool enabled = true;

        public static CameraSettings Snapshot(Camera camera)
        {
            if (camera == null) return null;
            var camT = camera.transform;
            var parent = camT.parent;
            Vector3 localPos = parent == null ? camT.position : parent.InverseTransformPoint(camT.position);
            Quaternion localRot = parent == null ? camT.rotation : Quaternion.Inverse(parent.rotation) * camT.rotation;
            return new CameraSettings
            {
                parent = parent,
                offset = localPos,
                eulerAngles = localRot.eulerAngles,
                nearClip = camera.nearClipPlane,
                farClip = camera.farClipPlane,
                cullingMask = camera.cullingMask,
                clearFlags = camera.clearFlags,
                depth = camera.depth,
                backgroundColor = camera.backgroundColor,
                orthographic = camera.orthographic,
                fov = camera.fieldOfView,
                orthoSize = camera.orthographicSize,
                enabled = camera.enabled
            };
        }

        public void ApplyTo(Camera camera)
        {
            if (camera == null) return;
            var camT = camera.transform;
            if (camT.parent != parent)
            {
                camT.SetParent(parent, false);
            }
            camT.localPosition = offset;
            camT.localEulerAngles = eulerAngles;
            camera.nearClipPlane = nearClip;
            camera.farClipPlane = farClip;
            camera.cullingMask = cullingMask;
            camera.clearFlags = clearFlags;
            camera.depth = depth;
            camera.backgroundColor = backgroundColor;
            camera.orthographic = orthographic;
            camera.fieldOfView = fov;
            camera.orthographicSize = orthoSize;
            camera.enabled = enabled;
        }
    }
    #endregion
}
